﻿/*
 Copyright (c) 2009 Alvin Cho (alvincho@risklib.com)
 Copyright (c) 2009 RiskLib.com
 
 This file is part of RiskLib.NET Project. See http://www.risklib.net for more information.
 
 RiskLib.NET is an open source project. Using of RiskLib.NET is free of charge.
 It is distributed under GNU General Public License version 2 (GPLv2).
 GPLv2 can be found at http://www.risklib.net/risklib/license.
 
 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/
/*
 * Part of this file are fetched from examples of Bloomberg Server API
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Bloomberglp.Blpapi;

namespace RiskLib.DataSource
{
    /// <summary>
    /// BloombergDataSource using Bloomberg Server API 3.2 to retrieve data from Bloomberg Professional Services
    /// Before using this class, please check your Bloomberg contract for availability and permission for using the API.
    /// </summary>
    public class BloombergDataSource : AbstractDataSource, IHistoricalSource, IRealTimeSource, IFundamentalSource
    {
        public BloombergDataSource(string host, int port)
            : base("Bloomberg")
        {
            serverHost = host;
            serverPort = port;
            if (!isOk())
                throw new Exception("No Bloomberg service found at " + host + ":" + port.ToString());
        }
        private FieldMappingList historicalFields = new FieldMappingList()
        {
			{FieldNameEnum.OpenPrice, "PX_OPEN"},
			{FieldNameEnum.HighPrice, "PX_HIGH"},
			{FieldNameEnum.LowPrice, "PX_LOW"},
			{FieldNameEnum.ClosePrice, "PX_LAST"},
			{FieldNameEnum.Volume, "PX_VOLUME"},
			{FieldNameEnum.Volatility260Days, "VOLATILITY_260D_CALC"},
			{FieldNameEnum.BidPrice, "PX_BID"},
			{FieldNameEnum.AskPrice, "PX_ASK"},
			{FieldNameEnum.CallImpliedVolatility, "HIST_CALL_IMP_VOL"},
			{FieldNameEnum.PutImpliedVolatility, "HIST_PUT_IMP_VOL"},
			{FieldNameEnum.VWAP, "EQY_WEIGHTED_AVG_PX"}

        };
        private FieldMappingList fundamentalFields = new FieldMappingList()
        {
            			{FieldNameEnum.ISIN, "ID_ISIN"},
			{FieldNameEnum.EnglishName, "NAME"},
			{FieldNameEnum.ChineseName, "Name_Chinese_Traditional"},
			{FieldNameEnum.Currency, "Crncy"},
			{FieldNameEnum.ExchangeCode, "Exch_Code"},
			{FieldNameEnum.EquityPrimaryExchangeCode, "Eqy_Prim_Exch"},
			{FieldNameEnum.Issuer, "Issuer"},
			{FieldNameEnum.IssuerTicker, "Issuer_PARENT_EQY_TICKER"},
			{FieldNameEnum.LastPrice, "PX_LAST"},
			{FieldNameEnum.BloombergUniqueCode, "ID_BB_UNIQUE"},
			{FieldNameEnum.Status, "Market Status"},
			{FieldNameEnum.UnderlyingBloombergUnique, "UNDL_ID_BB_UNIQUE"},
			{FieldNameEnum.Sector, "Industry_Sector"},
			{FieldNameEnum.IndustryGroup, "Industry_Group"},
			{FieldNameEnum.IndustrySubGroup, "Industry_SubGroup"},
			{FieldNameEnum.IndustrySubGroupNum, "Industry_SubGroup_Num"},
			{FieldNameEnum.BlpDesNotes, "DES Notes"},
			{FieldNameEnum.CompanyWebURL, "Company Web Address"},
			{FieldNameEnum.OutstandingShares, "Eqy Sh Out Actual"},
			{FieldNameEnum.OutstandingSharesDate, "Eqy Sh Out Dt"},
			{FieldNameEnum.HasConvertibles, "Has Convertibles"},
			{FieldNameEnum.HasOptions, "Eqy Opt Avail"},
			{FieldNameEnum.NextSplitDate, "Eqy Split Dt"},
			{FieldNameEnum.NextSplitRatio, "Eqy Split Ratio"},
			{FieldNameEnum.NextDividendDate, "Dvd Ex Dt"},
			{FieldNameEnum.NextDividendRatio, "Eqy Dvd Sh Last"},
			{FieldNameEnum.RoundLot, "PX_ROUND_LOT_SIZE"},
			{FieldNameEnum.IssueDate, "Issue Dt"},
			{FieldNameEnum.Maturity, "Maturity"},
			{FieldNameEnum.CouponType, "Cpn Typ"},
			{FieldNameEnum.Coupon, "Cpn"},
			{FieldNameEnum.FloatingSpread, "Flt Spread"},
			{FieldNameEnum.PaymentFrequency, "Cpn Freq"},
			{FieldNameEnum.RatingBloombergComposite, "Rtg BB Comp"},
			{FieldNameEnum.RatingSP, "Rtg Sp"},
			{FieldNameEnum.RatingMoodys, "Rtg Moody"},
			{FieldNameEnum.NextCouponDate, "Nxt Cpn Dt"},
			{FieldNameEnum.ConversionPrice, "Cv Cnvs Px"},
			{FieldNameEnum.NextPutDate, "Nxt Put Dt"},
			{FieldNameEnum.NextPutPrice, "Nxt Put Px"},
			{FieldNameEnum.Parity, "Cv Parity"},
			{FieldNameEnum.Redemption, "Redemp Val"},
			{FieldNameEnum.NextResetDate, "Nxt Refix Dt"},
			{FieldNameEnum.IsEstimatedCoupon, "Est Cpn Flag"},
			{FieldNameEnum.FloatingConvention, "Flt Cpn Convention"},
			{FieldNameEnum.FixedMethod, "Cpn Rate Fix Meth"},
			{FieldNameEnum.PrevFloatingCoupon, "Prev Flt Cpn"},
			{FieldNameEnum.ResetIndex, "Reset Idx"},
			{FieldNameEnum.FirstSettleDate, "First Settle Dt"},
			{FieldNameEnum.FirstCouponDate, "First Cpn Dt"},
			{FieldNameEnum.DayCountDescription, "Day Cnt Des"},
			{FieldNameEnum.AmountOutstanding, "Amt Outstanding"},
			{FieldNameEnum.AmountIssued, "Amt Issued"},
			{FieldNameEnum.ParAmount, "Par Amt"},
			{FieldNameEnum.ConversionStartDate, "Cv Start Dt"},
			{FieldNameEnum.ConversionEndDate, "Cv Until"},
			{FieldNameEnum.IssuePrice, "Issue Px"},
			{FieldNameEnum.Tenor, "Yrs To Mty Issue"},
			{FieldNameEnum.WarrantUnderlyingISIN, "WRT_UNDL_ISIN"},
			{FieldNameEnum.WarrantIssueAmount, "WRT_ISSUE_AMT"},
			{FieldNameEnum.WarrantIssuePrice, "WRT_ISSUE_PRC"},
			{FieldNameEnum.WarrantIsCovered, "WRT_COVERED"},
      {FieldNameEnum.WarrantSharesPerWarrant, "WRT_SH_PER"},
			{FieldNameEnum.StartTradingDate, "OPT_FIRST_TRADE_DT"},
			{FieldNameEnum.ExpiryDate, "OPT_EXPIRE_DT"},
			{FieldNameEnum.Strike, "OPT_STRIKE_PX"},
			{FieldNameEnum.CallPutType, "OPT_PUT_CALL"},
			{FieldNameEnum.OptionType, "OPT_EXER_TYP"},
			{FieldNameEnum.EndTradingDate, "FUT_LAST_TRADE_DT"},
      {FieldNameEnum.CheapestToDeliverISIN, "FUT_CTD_ISIN"},
      {FieldNameEnum.CheapestToDeliver, "FUT_CTD"},
      {FieldNameEnum.FirstDeliveryDate,"FUT_DLV_DT_FIRST"},
      {FieldNameEnum.LastDeliveryDate, "FUT_DLV_DT_LAST"},
      {FieldNameEnum.IsCashSettled, "CASH_SETTLED"},
      {FieldNameEnum.DeliverableBondsISIN, "FUT_DLVRBLE_BNDS_ISINS"},
      {FieldNameEnum.MonthlyGenericTicker, "FUT_MONTHLY_GEN_TICKER"}
        };
        private string serverHost;
        private int serverPort;

        private static readonly Name EXCEPTIONS
            = new Name("exceptions");
        private static readonly Name FIELD_ID = new Name("fieldId");
        private static readonly Name REASON = new Name("reason");
        private static readonly Name CATEGORY = new Name("category");
        private static readonly Name DESCRIPTION = new Name("description");

        public bool isOk()
        {
            if (!sessionStarted)
            {
                SessionOptions sessionOptions = new SessionOptions();
                sessionOptions.ServerHost = serverHost;
                //sessionOptions.ServerHost = "localhost";
                sessionOptions.ServerPort = serverPort;
                //sessionOptions.ServerPort = 8194;
                //System.Console.WriteLine("Connecting to " + serverHost + ":" + serverPort);
                session = new Session(sessionOptions);

                //rtSession = new Session(sessionOptions, new Bloomberglp.Blpapi.EventHandler(processEvent));
                //session.SetEventHandler(BlpEventHandler, Event.EventType.PARTIAL_RESPONSE);
                //session.SetEventHandler(BlpEventHandler, Event.EventType.RESPONSE);
                //bool sessionStarted = true;
                sessionStarted = session.Start();

                if (sessionStarted)
                    session.OpenService("//blp/refdata");
                //session.Stop();
                // Field Mapping
                //fieldMapping.Clear();
                //foreach (FieldNameEnum fName in FundamentalFields.Keys)
                //{
                //    fieldMapping.Add(fName, FundamentalFields[fName]);
                //}
                //foreach (FieldNameEnum fName in HistoricalFields.Keys)
                //{
                //    fieldMapping.Add(fName, HistoricalFields[fName]);
                //}
            }
            return sessionStarted;
        }
        bool sessionStarted = false;
        private Session session;
        private Session rtSession;
        public FieldNameEnum GetFieldName(string blpField)
        {
            foreach (FieldNameEnum fieldName in fundamentalFields.Keys)
            {
                if (fundamentalFields[fieldName].Replace(" ", "_").ToLower() == blpField.ToLower())
                    return fieldName;
            }
            foreach (FieldNameEnum fieldName in  historicalFields.Keys)
            {
                if (historicalFields[fieldName].Replace(" ", "_").ToLower() == blpField.ToLower())
                    return fieldName;
            }
            throw new ArgumentOutOfRangeException("Bloomberg.GetFieldName(): No field matched " + blpField);
        }
        public void processEvent(Event eventObj, Session session)
        {
            try
            {
                switch (eventObj.Type)
                {
                    case Event.EventType.SUBSCRIPTION_DATA:
                        processSubscriptionDataEvent(eventObj, session);
                        break;
                    case Event.EventType.SUBSCRIPTION_STATUS:
                        processSubscriptionStatus(eventObj, session);
                        break;
                    default:
                        processMiscEvents(eventObj, session);
                        break;
                }
            }
            catch (System.Exception e)
            {
                System.Console.WriteLine(e.ToString());
            }
        }

        private void processSubscriptionStatus(Event eventObj, Session session)
        {
            System.Console.WriteLine("Processing SUBSCRIPTION_STATUS");
            foreach (Message msg in eventObj.GetMessages())
            {
                string topic = (string)msg.CorrelationID.Object;
                System.Console.WriteLine(System.DateTime.Now.ToString("s") +
                    ": " + topic + " - " + msg.MessageType);

                if (msg.HasElement(REASON))
                {
                    // This can occur on SubscriptionFailure.
                    Element reason = msg.GetElement(REASON);
                    System.Console.WriteLine("\t" +
                            reason.GetElement(CATEGORY).GetValueAsString() +
                            ": " + reason.GetElement(DESCRIPTION).GetValueAsString());
                }

                if (msg.HasElement(EXCEPTIONS))
                {
                    // This can occur on SubscriptionStarted if at least
                    // one field is good while the rest are bad.
                    Element exceptions = msg.GetElement(EXCEPTIONS);
                    for (int i = 0; i < exceptions.NumValues; ++i)
                    {
                        Element exInfo = exceptions.GetValueAsElement(i);
                        Element fieldId = exInfo.GetElement(FIELD_ID);
                        Element reason = exInfo.GetElement(REASON);
                        System.Console.WriteLine("\t" + fieldId.GetValueAsString() +
                                ": " + reason.GetElement(CATEGORY).GetValueAsString());
                    }
                }
                System.Console.WriteLine("");
            }
        }

        private void processSubscriptionDataEvent(Event eventObj, Session session)
        {
            System.Console.WriteLine("Processing SUBSCRIPTION_DATA");
            foreach (Message msg in eventObj.GetMessages())
            {
                //string topic = (string)msg.CorrelationID.Object;
                //System.Console.WriteLine(System.DateTime.Now.ToString("s")
                //    + ": " + topic + " - " + msg.MessageType);

                string blpCode = msg.TopicName;
                double last = msg.GetElementAsFloat64(new Name("LAST_PRICE"));
                IProductCode productCode = new SimpleProductCode(blpCode);
                RealTimeDataEventArgs args = new RealTimeDataEventArgs();
                args.productCode = productCode;
                args.fieldName = FieldNameEnum.LastPrice;
                //args.dataSource = DataSourceEnum.Bloomberg;
                args.value = last;
                GotRealTimeData(args);
            }
        }

        private void processMiscEvents(Event eventObj, Session session)
        {
            //System.Console.WriteLine("Processing " + eventObj.Type);
            foreach (Message msg in eventObj.GetMessages())
            {
                SendMessage(System.DateTime.Now.ToString("s") +
                    ": " + msg.MessageType + "\n");
            }
        }
        public void ProcessRealTimeData(Element element)
        {
            string s = element.ToString();
        }
        public FundamentalDataList ProcessFundamentalData(Message msg, IProductCode product)
        {
            //FieldDataList fdList = product.GetFieldDataList();
            //FieldDataList fdList = new FieldDataList();
            FundamentalDataList fdList = new FundamentalDataList();
            try
            {

                Element securityData = (Element)msg.GetElement("securityData").GetValue(0);
                Element security = securityData.GetElement("security");
                Element fieldData = securityData.GetElement("fieldData");
                for (int i = 0; i < fieldData.NumValues; i++)
                {
                    //Element field = (Element)fieldData.GetValue(i);
                    foreach (Element ele in fieldData.Elements)
                    {
                        try
                        {
                            FieldString v = new FieldString();
                            FieldNameEnum fieldName = GetFieldName(ele.Name.ToString());
                            v.Data = ele.GetValueAsString();
                            if (fdList.ContainsKey(fieldName))
                                fdList[fieldName] = v;
                            else
                                fdList.Add(fieldName, v);
                        }
                        catch (Exception ex)
                        {
                            SendMessage("Bloomberg.ProdcessFundamentalData(" + ele.Name.ToString() + "): " + ex.Message);
                        }
                    }
                }

                FundamentalDataEventArgs args = new FundamentalDataEventArgs(fdList, "Got fundmantal for " + product.ProductCode);
                GotFundamentalData(args);
            }
            catch (Exception ex)
            {
                SendMessage("Bloomberg.ProdcessFundamentalData: " + ex.Message);
            }
            return fdList;
        }

        public bool Subscribe(string productCode, List<FieldNameEnum> fieldList)
        {
            List<IProductCode> result = new List<IProductCode>();
            result.Add(new SimpleProductCode(productCode));
            return Subscribe(result, fieldList);
        }
        public bool Subscribe(List<IProductCode> productList, List<FieldNameEnum> fList)
        {
            SessionOptions sessionOptions = new SessionOptions();
            sessionOptions.ServerHost = serverHost; //James
            sessionOptions.ServerPort = 8194;
            //System.Console.WriteLine("Connecting to " + serverHost + ":" + serverPort);
            rtSession = new Session(sessionOptions, new Bloomberglp.Blpapi.EventHandler(processEvent));
            //rtSession = new Session(sessionOptions);
            //rtSession = session;
            bool sessionStarted = rtSession.Start();
            if (!sessionStarted)
            {
                SendMessage("rtSession to Bloomberg not started.");
            }
            SendMessage("rtSession connected to " + sessionOptions.ServerHost + " started.");
            if (!rtSession.OpenService("//blp/mktdata"))
            {
                SendMessage("Failed to open //blp/mktdata");
                return false;
            }
            //SendMessage("Connected to //blp/mktdata");
            List<Subscription> subscriptions = new List<Subscription>();
            List<string> fields = new List<string>();
            foreach (FieldNameEnum fName in FundamentalFields.Keys)
            {
                fields.Add(FundamentalFields[fName]);
            }

            foreach (IProductCode productCode in productList)
            {
                string blpCode = productCode.GetProductCode(ProductCodeTypeEnum.Bloomberg);
                subscriptions.Add(new Subscription(
                                    blpCode,
                                    fields,
                                    new CorrelationID(blpCode))
                                  );
            }

            rtSession.Subscribe(subscriptions);
            return true;
        }

        public HistoricalDataList ProcessHistoricalData(Message msg, string productCode)
        {
            return ProcessHistoricalData(msg, new SimpleProductCode(productCode));
        }

        public HistoricalDataList ProcessHistoricalData(Message msg, IProductCode productCode)
        {
            HistoricalDataList hdList = new HistoricalDataList(productCode);
            try
            {
                Element securityData = msg.GetElement("securityData");
                Element security = securityData.GetElement("security");
                Element fieldData = securityData.GetElement("fieldData");
                for (int i = 0; i < fieldData.NumValues; i++)
                {
                    Element field = (Element)fieldData.GetValue(i);
                    HistoricalData hdata = new HistoricalData();
                    Datetime date = field.GetElementAsDate("date");
                    hdata.Date = date.ToSystemDateTime();
                    foreach (FieldNameEnum fName in HistoricalFields.Keys)
                    {
                        try
                        {
                            double v = field.GetElementAsFloat64(HistoricalFields[fName]);
                            hdata.Values.Add(fName, v);
                        }
                        catch { }
                    }

                    HistoricalDataEventArgs args = new HistoricalDataEventArgs();
                    args.product = productCode;
                    args.Data = hdata;
                    hdList.Add(hdata.Date, hdata);
                    GotHistoricalData(args);
                }
            }
            catch (Exception ex)
            {
                SendMessage("Bloomberg.ProdcessHistoicalData: " + ex.Message);
                throw ex;
            }
            return hdList;
        }


        public HistoricalDataList GetHistoricalData(IProduct product, DateTime startDate, DateTime endDate)
        {
            return GetHistoricalData(product, product.HistoricalFields, startDate, endDate);
        }

        public HistoricalDataList GetHistoricalData(IProduct product, List<FieldNameEnum> fieldList, DateTime startDate, DateTime endDate)
        {
            return GetHistoricalData(product.GetProductCode(ProductCodeTypeEnum.Bloomberg), fieldList, startDate, endDate);
        }

        public HistoricalDataList GetHistoricalData(IProductCode productCode, List<FieldNameEnum> fieldList, DateTime startDate, DateTime endDate)
        {
            return GetHistoricalData(productCode.ProductCode, fieldList, startDate, endDate);
        }
        public HistoricalDataList GetHistoricalData(string productCode, List<FieldNameEnum> fieldList, DateTime startDate, DateTime endDate)

        {
            HistoricalDataList hdList = new HistoricalDataList();
            //throw new Exception("Number of HistoricalFields: " + HistoricalFields.Count);
            try
            {
                Service refDataService = session.GetService("//blp/refdata");
                Request request = refDataService.CreateRequest("HistoricalDataRequest");
                Element securities = request.GetElement("securities");
                securities.AppendValue(productCode);
                Element fields = request.GetElement("fields");
                foreach (FieldNameEnum fName in fieldList)
                {
                    if (historicalFields.ContainsKey(fName))
                        fields.AppendValue(historicalFields[fName].Replace(" ", "_"));
                }
                request.Set("periodicityAdjustment", "ACTUAL");
                request.Set("periodicitySelection", "DAILY");
                request.Set("startDate", string.Format("{0:yyyyMMdd}", startDate));
                request.Set("endDate", string.Format("{0:yyyyMMdd}", endDate));
                //request.Set("maxDataPoints", 100);
                request.Set("returnEids", true);

                //System.Console.WriteLine("Sending Request: " + request);
                session.SendRequest(request, null);

                while (true)
                {
                    Event eventObj = session.NextEvent();

                    foreach (Message msg in eventObj.GetMessages())
                    {
                        if (msg.MessageType.ToString() == "HistoricalDataResponse")
                        {
                            hdList = ProcessHistoricalData(msg, productCode);
                        }
                    }
                    if (eventObj.Type == Event.EventType.RESPONSE)
                    {
                        break;
                    }
                }

            }
            catch (Exception ex)
            {
                SendMessage("Bloomberg.GetHistorical: " + ex.Message);
                throw ex;
            }
            return hdList;
        }

        public FundamentalDataList GetFundamentalData(IProduct product)
        {
            return GetFundamentalData(product, product.FundamentalFields);
        }

        public FundamentalDataList GetFundamentalData(IProduct product, List<FieldNameEnum> fieldList)
        {
            FundamentalDataList fdList = new FundamentalDataList();
            try
            {
                Service refDataService = session.GetService("//blp/refdata");

                Request request = refDataService.CreateRequest("ReferenceDataRequest");
                Element securities = request.GetElement("securities");
                string blpCode = product.GetProductCode(ProductCodeTypeEnum.Bloomberg);
                if (blpCode.Length == 0)
                    return null;
                securities.AppendValue(blpCode);
                Element fields = request.GetElement("fields");
                foreach (FieldNameEnum fName in fieldList)
                {
                    if (fieldList.Contains(fName))
                        fields.AppendValue(this.FundamentalFields[fName].Replace(" ", "_"));
                }

                System.Console.WriteLine("Sending Request: " + request);
                session.SendRequest(request, null);

                while (true)
                {
                    Event eventObj = session.NextEvent();
                    foreach (Message msg in eventObj.GetMessages())
                    {
                        if (msg.MessageType.ToString() == "ReferenceDataResponse")
                        {
                            fdList = ProcessFundamentalData(msg, product);
                        }
                    }
                    if (eventObj.Type == Event.EventType.RESPONSE)
                    {
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                SendMessage("Bloomberg.GetFundamental: " + ex.Message);
            }
            return fdList;
        }

        #region IHistoricalData 成員

        public FieldMappingList HistoricalFields
        {
            get
            {
                return historicalFields;
            }
            set
            {
                historicalFields = value;
            }
        }

        public event HistoricalDataEventHandler HistoricalDataEvent;

        public void GotHistoricalData(HistoricalDataEventArgs args)
        {
            if (HistoricalDataEvent != null)
                HistoricalDataEvent(this, args);
        }

        #endregion

        #region IRealTimeData 成員

        public FieldMappingList RealTimeFields
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public event RealTimeSourceEventHandler RealTimeDataEvent;


        public void GotRealTimeData(RealTimeDataEventArgs args)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IFundamentalData 成員

        public event FundamentalDataEventHandler FundamentalDataEvent;

        public FieldMappingList FundamentalFields
        {
            get
            {
                return fundamentalFields;
            }
            set
            {
                fundamentalFields = value;
            }
        }

        public void GotFundamentalData(FundamentalDataEventArgs args)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IRealTimeData 成員


        public bool Subscribe(IProductCode code, List<FieldNameEnum> fields)
        {
            throw new NotImplementedException();
        }

        #endregion


    }
}
